package com.ccc.utils;

import cn.hutool.core.util.BooleanUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.ccc.entity.Shop;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;


@Component
public class CacheClient {

    @Resource
    StringRedisTemplate stringRedisTemplate;

    public <R,ID> R queryWithMutex(String prefix, ID id, Class<R> type
            , Function<ID,R> dbFallBack){
        String key = prefix + id;
        //查询缓存
        String objJson = stringRedisTemplate.opsForValue().get(key);

        //缓存命中:返回
        if(StringUtils.isNotBlank(objJson)){
            return JSONUtil.toBean(objJson, type);
        }
        //缓存命中:为缓存空对象,直接返回
        if(objJson != null){
            return null;
        }
        //缓存未命中:为null->为特定商户对象设互斥锁
        if(!tryLock(id)){
            //未拿到锁的继续等待
            try {
                Thread.sleep(50);
                return queryWithMutex(prefix,id,type, dbFallBack);
                //递归, 若查询到则返回
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
        //若拿到锁的时候能够缓存命中,释放锁
        try {
            objJson = stringRedisTemplate.opsForValue().get(key);
            //缓存命中:返回
            if(StringUtils.isNotBlank(objJson)){
                return JSONUtil.toBean(objJson, type);
            }
            //查询数据库,重建缓存
            R r = dbFallBack.apply(id);

            //id不存在:创建缓存空对象,返回
            if (r == null) {
                stringRedisTemplate.opsForValue().set(key, "", RedisConstants.CACHE_NULL_TTL, TimeUnit.MINUTES);
                return null;
            }
            //id存在:创建缓存, 返回
            stringRedisTemplate.opsForValue()
                    .set(key, JSONUtil.toJsonStr(r), RedisConstants.CACHE_SHOP_TTL, TimeUnit.MINUTES);
            return r;
        }
        finally {
            //释放锁
            unLock(id);
        }
    }

    //自定义锁:成功->返回1,即ture
    private<ID> boolean tryLock(ID id){
        Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(RedisConstants.LOCK_SHOP_KEY+id,"1"
                , RedisConstants.LOCK_SHOP_TTL, TimeUnit.MINUTES);
        return BooleanUtil.isTrue(flag);
    }
    private <ID> void unLock(ID id){
        stringRedisTemplate.delete(RedisConstants.LOCK_SHOP_KEY+id);
    }
}
